﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using Pfz.DynamicObjects.Internal;

namespace Pfz.DynamicObjects
{
	/// <summary>
	/// Class responsible for building new classes at run-time.
	/// </summary>
	/// <typeparam name="T">The type of the user-instance that this dynamic-type will receive as argument.</typeparam>
	public sealed class DelegatedTypeBuilder<T>
	{
		#region Static area
			private static readonly Type[] _parameterTypes = new Type[]{typeof(T)};
			private static readonly ConstructorInfo _objectConstructor = typeof(object).GetConstructor(Type.EmptyTypes);
			private static readonly MethodInfo _dynamicActionInvoke = typeof(DelegatedDynamicAction<T>).GetMethod("Invoke");
		#endregion

		#region Private fields - used by many methods
			private int _id;
			private readonly DelegatedTypeBuilderConversions _conversions;
			internal readonly TypeBuilder _type;
			private readonly FieldBuilder _userInstanceField;
			private readonly FieldBuilder _actionsField;
			private ILGenerator _constructorGenerator;
			private List<Delegate> _actions = new List<Delegate>();
			private Delegate[] _arrayActions;
		#endregion
		
		#region Constructor
			/// <summary>
			/// Creates a new instance that will use the given conversions and that will
			/// generate types that implement the given interfaces.
			/// Note: It is up to you to add all the needed methods to make the interfaces work.
			/// </summary>
			public DelegatedTypeBuilder(DelegatedTypeBuilderConversions conversions, bool generateCollectibleAssembly=false, params Type[] interfacesToImplement)
			{
				if (!typeof(T).IsVisible)
					throw new ArgumentException(typeof(T).FullName + " must be public.");

				if (interfacesToImplement != null)
				{
					foreach(var interfaceType in interfacesToImplement)
					{
						if (interfaceType == null)
							throw new ArgumentException("interfacesToImplement can't have null values.");
							
						if (!interfaceType.IsVisible)
							throw new ArgumentException("interfacesToImplement can't have non-public interfaces.");
					}
				}
				
				int id = _id++;
				if (generateCollectibleAssembly)
				{
					var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Assembly"), AssemblyBuilderAccess.RunAndCollect);
					var module = assembly.DefineDynamicModule("Module");
					_type = module.DefineType("Class" + id, TypeAttributes.Public | TypeAttributes.Sealed, typeof(object), interfacesToImplement);
				}
				else
				{
					_type = _DynamicModule.DefineType("Class" + id, TypeAttributes.Public | TypeAttributes.Sealed, typeof(object), interfacesToImplement);
				}

				_conversions = conversions;

				_userInstanceField = _type.DefineField("<<userInstance>>", typeof(T), FieldAttributes.Private | FieldAttributes.InitOnly);
				_actionsField = _type.DefineField("<<actions>>", typeof(Delegate[]), FieldAttributes.Public | FieldAttributes.Static);
				var constructor = _type.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, _parameterTypes);
				var generator = constructor.GetILGenerator();
				generator.Emit(OpCodes.Ldarg_0);
				generator.Emit(OpCodes.Call, _objectConstructor);
				generator.Emit(OpCodes.Ldarg_0);
				generator.Emit(OpCodes.Ldarg_1);
				generator.Emit(OpCodes.Stfld, _userInstanceField);
				_constructorGenerator = generator;
				
				var createMethod = _type.DefineMethod("<<Create>>", MethodAttributes.Static | MethodAttributes.Public, typeof(object), _parameterTypes);
				generator = createMethod.GetILGenerator();
				generator.Emit(OpCodes.Ldarg_0);
				generator.Emit(OpCodes.Newobj, constructor);
				generator.Emit(OpCodes.Ret);
			}
		#endregion
		#region Methods
			#region _AddOrReuseHandler
				private int _AddOrReuseHandler(Delegate action)
				{
					int index = _actions.IndexOf(action);
					if (index != -1)
						return index;
						
					index = _actions.Count;
					_actions.Add(action);
					return index;
				}
			#endregion
			#region _LoadUserInstance
				private void _LoadUserInstance(ILGenerator generator)
				{
					generator.Emit(OpCodes.Ldarg_0);
					generator.Emit(OpCodes.Ldfld, _userInstanceField);
				}
			#endregion
			#region _ConvertParameter
				private void _ConvertParameter(ref _ActionsList disposableList, string parameterName, bool isOut, Type toType, Type fromType, ILGenerator generator, Action stackValueAction)
				{
					bool isByRef = fromType.IsByRef;
					if (isByRef != toType.IsByRef)
						throw new NotSupportedException("Conversion from ref parameters to non-ref parameters is not supported.");

					if (toType == typeof(void))
					{
						stackValueAction();

						if (fromType != typeof(void))
							generator.Emit(OpCodes.Pop);

						return;
					}

					if (fromType == typeof(void))
					{
						stackValueAction();
						generator.Emit(OpCodes.Ldnull);
						return;
					}

					var innerFromType = fromType;
					var innerToType = toType;
					if (isByRef)
					{
						innerFromType = fromType.GetElementType();
						innerToType = toType.GetElementType();
					}

					if (_conversions != null)
					{
						var conversion = _conversions.TryGetConverter(innerFromType, innerToType);
						if (conversion != null)
						{
							var actionIndex = _AddOrReuseHandler(conversion);
							var conversionType = conversion.GetType();

							if (!isOut)
							{
								// stacks the conversion action
								generator.Emit(OpCodes.Ldsfld, _actionsField);
								generator.Emit(OpCodes.Ldc_I4, actionIndex);
								generator.Emit(OpCodes.Ldelem, conversionType);

								if (conversionType.GetGenericTypeDefinition() == typeof(Func<,,>))
									generator.Emit(OpCodes.Ldstr, parameterName);

							}

							// stacks the value
							stackValueAction();

							LocalBuilder firstLocal = null;
							LocalBuilder newLocalFrom = null;
							LocalBuilder newLocalTo = null;
							if (isByRef)
							{
								firstLocal = generator.DeclareLocal(innerFromType);
								newLocalFrom = generator.DeclareLocal(innerFromType);
								newLocalTo = generator.DeclareLocal(innerToType);

								generator.Emit(OpCodes.Stloc, firstLocal);

								if (!isOut)
								{
									generator.Emit(OpCodes.Ldloc, firstLocal);
									generator.Emit(OpCodes.Ldobj, innerFromType);
								}
							}

							if (!isOut)
							{
								// does the call... the return is already stacked, so keep it there.
								generator.Emit(OpCodes.Callvirt, conversionType.GetMethod("Invoke"));
							}

							if (isByRef)
							{
								if (!isOut)
									generator.Emit(OpCodes.Stloc, newLocalTo);

								generator.Emit(OpCodes.Ldloca, newLocalTo);

								Action endAction =
									() =>
									{
										var conversion2 = _conversions.TryGetConverter(innerToType, innerFromType);
										if (conversion2 == null)
											throw new InvalidOperationException("When a conversion is available to a ref or out field, the opposite conversion must also be available. Missing: " + innerToType.FullName + " to " + innerFromType.FullName);

										var actionIndex2 = _AddOrReuseHandler(conversion2);
										var conversionType2 = conversion2.GetType();

										// this is needed before everything to keep the stacked values
										// in order.
										generator.Emit(OpCodes.Ldloc, firstLocal);

										// stacks the conversion action
										generator.Emit(OpCodes.Ldsfld, _actionsField);
										generator.Emit(OpCodes.Ldc_I4, actionIndex2);
										generator.Emit(OpCodes.Ldelem, conversionType2);

										if (conversionType2.GetGenericTypeDefinition() == typeof(Func<,,>))
											generator.Emit(OpCodes.Ldstr, parameterName);

										// gets the value
										generator.Emit(OpCodes.Ldloc, newLocalTo);

										// does the call.
										generator.Emit(OpCodes.Callvirt, conversionType2.GetMethod("Invoke"));

										// Now we must store the returned value into the address of the initial variable.
										generator.Emit(OpCodes.Stobj, innerFromType);
									};

								if (disposableList == null)
									disposableList = new _ActionsList();

								disposableList.Add(endAction);
							}

							return;
						}
					}

					if (fromType == toType)
					{
						stackValueAction();
						return;
					}

					if (isByRef)
					{
						if (isOut && innerFromType.IsAssignableFrom(innerToType) && !innerToType.IsValueType)
						{
							stackValueAction();
							return;
						}

						throw new NotSupportedException("At this moment only explicit conversions are accepted to ref and out parameters or out parameters that use a more base type for reference-types.");
					}

					// you try to put a Disposable (maybe struct) into an IDisposable.
					if (toType.IsAssignableFrom(fromType))
					{
						stackValueAction();

						if (fromType.IsValueType)
							generator.Emit(OpCodes.Box, fromType);

						return;
					}

					// you try to pass an object to an string. A cast may solve the case.
					if (fromType.IsAssignableFrom(toType))
					{
						stackValueAction();
						generator.Emit(OpCodes.Castclass, toType);

						if (toType.IsValueType)
							generator.Emit(OpCodes.Unbox_Any, toType);

						return;
					}

					throw new ArgumentException("There is no valid conversion from type " + fromType.FullName + " to type " + toType.FullName);
				}
			#endregion
			#region _LoadParameters
				private _ActionsList _LoadParameters(ParameterInfo[] actionParameters, Type[] receivedParameterTypes, ILGenerator generator)
				{
					int count = actionParameters.Length;

					if (receivedParameterTypes != null && receivedParameterTypes.Length != count)
						throw new ArgumentException("The action has a different number of parameter types than expected.");

					_ActionsList result = null;

					for (int i = 0; i < count; i++)
					{
						var parameter = actionParameters[i];
						var toType = parameter.ParameterType;
						var fromType = toType;
						bool isOut = parameter.IsOut;

						if (receivedParameterTypes != null)
							fromType = receivedParameterTypes[i];

						int iPlus1 = i + 1;
						_ConvertParameter(ref result, parameter.Name, isOut, toType, fromType, generator, () => generator.Emit(OpCodes.Ldarg, iPlus1));
					}

					return result;
				}
			#endregion
			
			#region AddStaticMethod
				/// <summary>
				/// Static methods do not receive the userInstance value so all the parameters they receive 
				/// are passed directly to the action. They are not really static when implemented.
				/// </summary>
				public MethodBuilder AddStaticMethod(string name, Delegate action)
				{
					if (name == null)
						throw new ArgumentNullException("name");

					if (action == null)
						throw new ArgumentNullException("action");

					int index = _AddOrReuseHandler(action);
					
					var actionMethod = action.Method;
					var returnType = actionMethod.ReturnType;
					var parameters = actionMethod.GetParameters();
					int parameterCount = parameters.Length;
					var actionParameterTypes = new Type[parameterCount];
					for(int i=0; i<parameterCount; i++)
						actionParameterTypes[i] = parameters[i].ParameterType;
					
					var method = _type.DefineMethod(name, MethodAttributes.Public | MethodAttributes.Virtual, returnType, actionParameterTypes);
					var generator = method.GetILGenerator();
					var actionType = action.GetType();
					
					generator.Emit(OpCodes.Ldsfld, _actionsField);
					generator.Emit(OpCodes.Ldc_I4, index);
					generator.Emit(OpCodes.Ldelem, actionType);

					using(_LoadParameters(parameters, null, generator))
						generator.Emit(OpCodes.Callvirt, actionType.GetMethod("Invoke"));
						
					generator.Emit(OpCodes.Ret);
					return method;
				}
			#endregion
			#region AddMethod
				/// <summary>
				/// Instance methods will call the action passing the userInstance as the first
				/// member (like the this on normal instance methods).
				/// </summary>
				public MethodBuilder AddMethod(string name, Delegate action)
				{
					return AddMethod(name, action, null, null);
				}
				
				/// <summary>
				/// Adds a method that will use the given action to execute and allows you to
				/// specify different return type and parameter types. The data-type conversions will
				/// be used in this case.
				/// </summary>
				public MethodBuilder AddMethod(string name, Delegate action, Type generatedReturnType, params Type[] receivedParameterTypes)
				{
					if (name == null)
						throw new ArgumentNullException("name");

					if (action == null)
						throw new ArgumentNullException("action");

					var actionMethod = action.Method;
					var actionReturnType = actionMethod.ReturnType;
					var parameters = actionMethod.GetParameters();
					int parameterCount = parameters.Length;
					if (parameterCount == 0)
						throw new ArgumentException("action must receive at least one parameter (the userInstance).", "action");
						
					int index = _AddOrReuseHandler(action);

					parameterCount--;
					var actionParameterTypes = new Type[parameterCount];
					var actionParameters = new ParameterInfo[parameterCount];
					for (int i = 0; i < parameterCount; i++)
					{
						var parameter = parameters[i+1];
						actionParameters[i] = parameter;
						actionParameterTypes[i] = parameter.ParameterType;
					}
						
					var typesToUse = receivedParameterTypes;
					if (typesToUse == null)
						typesToUse = actionParameterTypes;

					if (generatedReturnType == null)
						generatedReturnType = actionReturnType;

					var method = _type.DefineMethod(name, MethodAttributes.Public | MethodAttributes.Virtual, generatedReturnType, typesToUse);
					var generator = method.GetILGenerator();
					var actionType = action.GetType();

					_ActionsList disposableList = null;
					try
					{
						_ConvertParameter
						(
							ref disposableList,
							"return",
							false,
							generatedReturnType, actionReturnType, generator,
							() =>
							{
								generator.Emit(OpCodes.Ldsfld, _actionsField);
								generator.Emit(OpCodes.Ldc_I4, index);
								generator.Emit(OpCodes.Ldelem, actionType);

								var firstParameter = parameters[0];
								_ConvertParameter(ref disposableList, firstParameter.Name, false, firstParameter.ParameterType, typeof(T), generator, () => _LoadUserInstance(generator));
								
								using(_LoadParameters(actionParameters, receivedParameterTypes, generator))
									generator.Emit(OpCodes.Callvirt, actionType.GetMethod("Invoke"));
							}
						);
					}
					finally
					{
						if (disposableList != null)
							disposableList.Dispose();
					}
					
					generator.Emit(OpCodes.Ret);
					
					return method;
				}
			#endregion
			#region AddDynamicMethod
				/// <summary>
				/// Adds a "dynamic method". That is, the method is added normally, but will call an action
				/// capable of executing independent on the number of arguments.
				/// Such dynamism makes things a little slower, though.
				/// </summary>
				public MethodBuilder AddDynamicMethod(string name, DelegatedDynamicAction<T> action, Type returnType, params Type[] parameterTypes)
				{
					if (name == null)
						throw new ArgumentNullException("name");
						
					if (returnType == null)
						returnType = typeof(void);
						
					if (parameterTypes != null)
						foreach(var parameterType in parameterTypes)
							if (parameterType == null || parameterType == typeof(void))
								throw new ArgumentException("parameterTypes can't contain null values or typeof(void).", "parameterTypes");
								
					if (action == null)
						throw new ArgumentNullException("action");
						
					int actionIndex = _AddOrReuseHandler(action);

					var result = _type.DefineMethod(name, MethodAttributes.Public | MethodAttributes.Virtual, returnType, parameterTypes);
					var generator = result.GetILGenerator();

					// stack the action itself.
					generator.Emit(OpCodes.Ldsfld, _actionsField);
					generator.Emit(OpCodes.Ldc_I4, actionIndex);
					generator.Emit(OpCodes.Ldelem, typeof(DelegatedDynamicAction<T>));
					
					// stack the userInstance.
					generator.Emit(OpCodes.Ldarg_0);
					generator.Emit(OpCodes.Ldfld, _userInstanceField);

					// stack the values
					if (parameterTypes == null || parameterTypes.Length == 0)
						generator.Emit(OpCodes.Ldnull);
					else
					{
						int count = parameterTypes.Length;
						generator.Emit(OpCodes.Ldc_I4, count);
						generator.Emit(OpCodes.Newarr, typeof(object));
						
						var arrayVariable = generator.DeclareLocal(typeof(object[]));
						generator.Emit(OpCodes.Stloc, arrayVariable);
						for(int i=0; i<count; i++)
						{
							generator.Emit(OpCodes.Ldloc, arrayVariable);
							generator.Emit(OpCodes.Ldc_I4, i);
							generator.Emit(OpCodes.Ldarg, i+1);
							
							var parameterType = parameterTypes[i];
							if (parameterType.IsValueType)
								generator.Emit(OpCodes.Box, parameterType);
								
							generator.Emit(OpCodes.Stelem, typeof(object));
						}
						
						generator.Emit(OpCodes.Ldloc, arrayVariable);
					}

					generator.Emit(OpCodes.Callvirt, _dynamicActionInvoke);

					if (returnType == typeof(void))
						generator.Emit(OpCodes.Pop);
					else
					if (returnType.IsValueType)
						generator.Emit(OpCodes.Unbox_Any, returnType);

					generator.Emit(OpCodes.Ret);
					return result;
				}
			#endregion
			#region AddProperty
				/// <summary>
				/// Adds a property that will redirect to the given get or set actions.
				/// If one of the values is null, the getter or setter will not be generated.
				/// </summary>
				public PropertyBuilder AddProperty<TProperty>(string name, Func<T, TProperty> getAction, Action<T, TProperty> setAction)
				{
					return AddProperty(name, typeof(TProperty), getAction, setAction);
				}
				
				/// <summary>
				/// Adds a property that will redirect to ghe given get or set, but allows getters and setters
				/// of different types to be given. The Conversions will be used if the types are different.
				/// </summary>
				public PropertyBuilder AddProperty(string name, Type propertyType, Delegate getAction, Delegate setAction)
				{
					if (string.IsNullOrEmpty(name))
						throw new ArgumentNullException("name");
						
					if (getAction == null && setAction == null)
						throw new ArgumentException("getAction and setAction can't be both null.");

					var result = _type.DefineProperty(name, PropertyAttributes.None, propertyType, Type.EmptyTypes);
					if (getAction != null)
					{
						var getMethod = AddMethod("get_" + name, getAction, propertyType, null);
						result.SetGetMethod(getMethod);
					}

					if (setAction != null)
					{
						var setMethod = AddMethod("set_" + name, setAction, typeof(void), propertyType);
						result.SetSetMethod(setMethod);
					}
						
					return result;
				}
			#endregion
			#region AddPropertyWithField
				/// <summary>
				/// Adds a property with its respective field (for fast access).
				/// It can then call a get method (providing the fieldValue) and/or a set method (providing the field 
				/// value as a reference and a new value). If those are not provided, the direct get/set will be
				/// implemented.
				/// </summary>
				/// <typeparam name="TProperty">The type of the property to create.</typeparam>
				/// <param name="name">The name of the property to create.</param>
				/// <param name="getAction">The action to invoke for get. If it is not provided, it will be auto-implemented.</param>
				/// <param name="setAction">The action to invoke for set. If it is not provided, it will be auto-implemented.</param>
				/// <param name="initializeAction">An action used to initialize the field (if needed).</param>
				/// <returns>The generated PropertyBuilder.</returns>
				public PropertyBuilder AddPropertyWithField<TProperty>(string name, DelegatedPropertyWithFieldGet<T, TProperty, TProperty> getAction, DelegatedPropertyWithFieldSet<T, TProperty, TProperty> setAction, DelegatedFieldInitialize<T, TProperty> initializeAction)
				{
					return AddPropertyWithField<TProperty, TProperty>(name, getAction, setAction, initializeAction);
				}

				// TODO make this version support delegates in general.
				// TODO check the types and, if they are ok, do the actual implementation. This will make
				// the lazy loader generator more versatile (and really useable).
				/// <summary>
				/// Adds a property with its respective field (for fast access).
				/// It can then call a get method (providing the fieldValue) and/or a set method (providing the field 
				/// value as a reference and a new value). If those are not provided, the direct get/set will be
				/// implemented.
				/// </summary>
				/// <typeparam name="TProperty">The type of the property to create.</typeparam>
				/// <typeparam name="TField">The type of the field to create, which can be different from the property type.</typeparam>
				/// <param name="name">The name of the property to create.</param>
				/// <param name="getAction">The action to invoke for get. If it is not provided, it will be auto-implemented if TField and TProperty are the same type.</param>
				/// <param name="setAction">The action to invoke for set. If it is not provided, it will be auto-implemented if TField and TProperty are the same type.</param>
				/// <param name="initializeAction">An action used to initialize the field (if needed).</param>
				/// <returns>The generated PropertyBuilder.</returns>
				public PropertyBuilder AddPropertyWithField<TProperty, TField>(string name, DelegatedPropertyWithFieldGet<T, TProperty, TField> getAction, DelegatedPropertyWithFieldSet<T, TProperty, TField> setAction, DelegatedFieldInitialize<T, TField> initializeAction)
				{
					if (string.IsNullOrEmpty(name))
						throw new ArgumentNullException("name");
						
					if (getAction == null && setAction == null && typeof(TProperty) != typeof(TField))
						throw new ArgumentException("getAction and setAction can't be null if TField and TProperty are different.");

					var result = _type.DefineProperty(name, PropertyAttributes.None, typeof(TProperty), Type.EmptyTypes);
					var field = _type.DefineField("<<field>>" + name, typeof(TField), FieldAttributes.Private);
					if (getAction != null || typeof(TProperty) == typeof(TField))
					{
						var getMethod = _type.DefineMethod("get_" + name, MethodAttributes.Public | MethodAttributes.Virtual, typeof(TProperty), Type.EmptyTypes);
						var getGenerator = getMethod.GetILGenerator();
						
						if (getAction == null)
						{
							// only loads the field value.
							getGenerator.Emit(OpCodes.Ldarg_0);	
							getGenerator.Emit(OpCodes.Ldfld, field);
						}
						else
						{
							var actionIndex = _AddOrReuseHandler(getAction);

							// stack the action itself.
							getGenerator.Emit(OpCodes.Ldsfld, _actionsField);
							getGenerator.Emit(OpCodes.Ldc_I4, actionIndex);
							getGenerator.Emit(OpCodes.Ldelem, typeof(DelegatedDynamicAction<T>));

							// stack the userInstance.
							getGenerator.Emit(OpCodes.Ldarg_0);
							getGenerator.Emit(OpCodes.Ldfld, _userInstanceField);
							
							// stack the field value
							getGenerator.Emit(OpCodes.Ldarg_0);
							getGenerator.Emit(OpCodes.Ldfld, field);
							
							getGenerator.Emit(OpCodes.Callvirt, typeof(DelegatedPropertyWithFieldGet<T, TProperty, TField>).GetMethod("Invoke"));
						}
						getGenerator.Emit(OpCodes.Ret);
						result.SetGetMethod(getMethod);
					}
					
					if (setAction != null || typeof(TProperty) == typeof(TField))
					{
						var setMethod = _type.DefineMethod("set_" + name, MethodAttributes.Public | MethodAttributes.Virtual, typeof(void), new Type[] { typeof(TProperty) });
						var setGenerator = setMethod.GetILGenerator();

						if (setAction == null)
						{
							setGenerator.Emit(OpCodes.Ldarg_0);
							setGenerator.Emit(OpCodes.Ldarg_1);
							setGenerator.Emit(OpCodes.Stfld, field);
						}
						else
						{
							var actionIndex = _AddOrReuseHandler(setAction);

							// stack the action itself.
							setGenerator.Emit(OpCodes.Ldsfld, _actionsField);
							setGenerator.Emit(OpCodes.Ldc_I4, actionIndex);
							setGenerator.Emit(OpCodes.Ldelem, typeof(DelegatedDynamicAction<T>));

							// stack the userInstance.
							setGenerator.Emit(OpCodes.Ldarg_0);
							setGenerator.Emit(OpCodes.Ldfld, _userInstanceField);

							// stack the field reference
							setGenerator.Emit(OpCodes.Ldarg_0);
							setGenerator.Emit(OpCodes.Ldflda, field);
							
							// stack the value passed as parameter
							setGenerator.Emit(OpCodes.Ldarg_1);

							setGenerator.Emit(OpCodes.Callvirt, typeof(DelegatedPropertyWithFieldSet<T, TProperty, TField>).GetMethod("Invoke"));
						}
						setGenerator.Emit(OpCodes.Ret);
						result.SetSetMethod(setMethod);
					}
					
					if (initializeAction != null)
					{
						var actionIndex = _AddOrReuseHandler(initializeAction);
						
						_constructorGenerator.Emit(OpCodes.Ldarg_0);

						// stack the action itself.
						_constructorGenerator.Emit(OpCodes.Ldsfld, _actionsField);
						_constructorGenerator.Emit(OpCodes.Ldc_I4, actionIndex);
						_constructorGenerator.Emit(OpCodes.Ldelem, typeof(DelegatedDynamicAction<T>));

						// stack the userInstance.
						_constructorGenerator.Emit(OpCodes.Ldarg_0);
						_constructorGenerator.Emit(OpCodes.Ldfld, _userInstanceField);

						_constructorGenerator.Emit(OpCodes.Callvirt, typeof(DelegatedFieldInitialize<T, TField>).GetMethod("Invoke"));
						_constructorGenerator.Emit(OpCodes.Stfld, field);
						
					}

					return result;
				}
			#endregion
			#region AddEvent
				/// <summary>
				/// Adds an event that will use the given actions to "add" and to "remove" handlers.
				/// </summary>
				public EventBuilder AddEvent<TEvent>(string name, DelegatedEventAddRemove<T, TEvent> addAction, DelegatedEventAddRemove<T, TEvent> removeAction)
				{
					if (string.IsNullOrEmpty(name))
						throw new ArgumentNullException("name");
						
					if (addAction == null)
						throw new ArgumentNullException("addAction");
						
					if (removeAction == null)
						throw new ArgumentNullException("removeAction");
						
					var result = _type.DefineEvent(name, EventAttributes.None, typeof(TEvent));
					
					var addMethod = AddMethod("add_" + name, addAction);
					result.SetAddOnMethod(addMethod);
					
					var removeMethod = AddMethod("remove_" + name, removeAction);
					result.SetRemoveOnMethod(removeMethod);
					return result;
				}
			#endregion

			#region AdaptToUserInstance
				/// <summary>
				/// This will create a method with the given name, returnType and parameterTypes that
				/// will call a method on the userInstance.
				/// Note: The afterAction is optional and maybe in the future it will accept extra parameters.
				/// </summary>
				public MethodBuilder AdaptToUserInstance(MethodInfo methodToCall, string name, Delegate getInnerInstance, Action<T> afterAction, Type returnType, params Type[] parameterTypes)
				{
					if (methodToCall == null)
						throw new ArgumentNullException("methodToCall");
						
					var userInstanceType = typeof(T);
					if (getInnerInstance != null)
					{
						var getInnerInstanceMethod = getInnerInstance.Method;
						userInstanceType = getInnerInstanceMethod.ReturnType;
						var getInnerInstanceParameters = getInnerInstanceMethod.GetParameters();
						if (getInnerInstanceParameters.Length != 1)
							throw new ArgumentException("getInnerInstance must point to a method that receive a single parameter.");
							
						if (getInnerInstanceParameters[0].ParameterType != typeof(T))
							throw new ArgumentException("getInnerInstance must point to a method that receives a single parameter of type " + typeof(T).FullName);
					}
					
					if (!methodToCall.IsStatic)
					{
						if (!methodToCall.ReflectedType.IsAssignableFrom(userInstanceType))
							throw new ArgumentException("methodToCall must be static or must be from type " + userInstanceType);
					}
						
					if (name == null)
						throw new ArgumentNullException("name");
						
					if (returnType == null)
						returnType = methodToCall.ReturnType;
					
					var methodParameters = methodToCall.GetParameters();
					int parameterCount = methodParameters.Length;
					var methodParameterTypes = new Type[parameterCount];
					for(int i=0; i<parameterCount; i++)
						methodParameterTypes[i] = methodParameters[i].ParameterType;
					
					if (parameterTypes == null)
						parameterTypes = methodParameterTypes;
					else	
					if (parameterTypes.Length != methodParameterTypes.Length)
						throw new ArgumentException("parameterTypes has the wrong number of values.");

					_ActionsList disposableList = null;
					var newMethod = _type.DefineMethod(name, MethodAttributes.Public | MethodAttributes.Virtual, returnType, parameterTypes);
					var generator = newMethod.GetILGenerator();
					
					try
					{
						_ConvertParameter
						(
							ref disposableList,
							"return",
							false,
							returnType,
							methodToCall.ReturnType,
							generator,
							() =>
							{
								if (!methodToCall.IsStatic)
								{
									if (getInnerInstance != null)
									{
										var getInnerInstanceType = getInnerInstance.GetType();
										var getInnerInstanceIndex = _AddOrReuseHandler(getInnerInstance);
										generator.Emit(OpCodes.Ldsfld, _actionsField);
										generator.Emit(OpCodes.Ldc_I4, getInnerInstanceIndex);
										generator.Emit(OpCodes.Ldelem, getInnerInstanceType);
										
										_LoadUserInstance(generator);
										
										generator.Emit(OpCodes.Callvirt, getInnerInstanceType.GetMethod("Invoke"));

										if (userInstanceType.IsValueType)
										{
											if (methodToCall.DeclaringType == userInstanceType)
											{
												var local = generator.DeclareLocal(userInstanceType);
												generator.Emit(OpCodes.Stloc, local);
												generator.Emit(OpCodes.Ldloca, local);
											}
											else
												generator.Emit(OpCodes.Box, userInstanceType);
										}
									}
									else
									{
										generator.Emit(OpCodes.Ldarg_0);
										if (userInstanceType.IsValueType)
										{
											if (methodToCall.DeclaringType == userInstanceType)
												generator.Emit(OpCodes.Ldflda, _userInstanceField);
											else
											{
												generator.Emit(OpCodes.Ldfld, _userInstanceField);
												generator.Emit(OpCodes.Box, userInstanceType);
											}
										}
										else
											generator.Emit(OpCodes.Ldfld, _userInstanceField);
									}
								}
									
								using(_LoadParameters(methodParameters, parameterTypes, generator))
								{
									if (methodToCall.IsVirtual && !typeof(T).IsValueType)
										generator.Emit(OpCodes.Callvirt, methodToCall);
									else
										generator.Emit(OpCodes.Call, methodToCall);
								}
							}
						);
					}
					finally
					{
						if (disposableList != null)
							disposableList.Dispose();
					}
					
					if (afterAction != null)
					{
						var actionIndex = _AddOrReuseHandler(afterAction);

						generator.Emit(OpCodes.Ldsfld, _actionsField);
						generator.Emit(OpCodes.Ldc_I4, actionIndex);
						generator.Emit(OpCodes.Ldelem, typeof(Action<T>));
						
						_LoadUserInstance(generator);

						generator.Emit(OpCodes.Callvirt, typeof(Action<T>).GetMethod("Invoke"));
					}
					
					generator.Emit(OpCodes.Ret);
					
					return newMethod;
				}
			#endregion
			#region AdaptInstanceCreation
				/// <summary>
				/// This will create a method with the given name, returnType and parameterTypes that
				/// will call a method on the userInstance.
				/// Note: The afterAction is optional and maybe in the future it will accept extra parameters.
				/// </summary>
				public MethodBuilder AdaptInstanceCreation(ConstructorInfo constructorToCall, string newMethodName, Type newReturnType, params Type[] parameterTypes)
				{
					if (constructorToCall == null)
						throw new ArgumentNullException("constructorToCall");
						
					if (newMethodName == null)
						throw new ArgumentNullException("newMethodName");
						
					if (newReturnType == null)
						newReturnType = constructorToCall.DeclaringType;
					
					var methodParameters = constructorToCall.GetParameters();
					int parameterCount = methodParameters.Length;
					var methodParameterTypes = new Type[parameterCount];
					for(int i=0; i<parameterCount; i++)
						methodParameterTypes[i] = methodParameters[i].ParameterType;
					
					if (parameterTypes == null)
						parameterTypes = methodParameterTypes;
					else	
					if (parameterTypes.Length != methodParameterTypes.Length)
						throw new ArgumentException("parameterTypes has the wrong number of values.");

					_ActionsList disposableList = null;
					var newMethod = _type.DefineMethod(newMethodName, MethodAttributes.Public | MethodAttributes.Virtual, newReturnType, parameterTypes);
					var generator = newMethod.GetILGenerator();
					
					try
					{
						_ConvertParameter
						(
							ref disposableList,
							"return",
							false,
							newReturnType,
							constructorToCall.DeclaringType,
							generator,
							() =>
							{
								using(_LoadParameters(methodParameters, parameterTypes, generator))
									generator.Emit(OpCodes.Newobj, constructorToCall);
							}
						);
					}
					finally
					{
						if (disposableList != null)
							disposableList.Dispose();
					}
					
					generator.Emit(OpCodes.Ret);
					
					return newMethod;
				}
			#endregion
		
			#region CreateCreator
				private Func<T, object> _creator;
				/// <summary>
				/// Gets the functions responsable for creating new instances.
				/// After getting this function, this type-builder should not be used anymore (even
				/// this method should not be called twice).
				/// </summary>
				public Func<T, object> CreateCreator()
				{
					var creator = _creator;
					
					if (creator == null)
					{
						_arrayActions = _actions.ToArray();
						_actions = null;
						_constructorGenerator.Emit(OpCodes.Ret);
						_constructorGenerator = null;
						
						var generatedType = _type.CreateType();
						generatedType.GetField("<<actions>>").SetValue(null, _arrayActions);
						var createMethod = generatedType.GetMethod("<<Create>>");
						creator = (Func<T, object>)Delegate.CreateDelegate(typeof(Func<T, object>), createMethod);
						_creator = creator;
					}
					
					return creator;
				}
			#endregion
		#endregion
	}
}
